package org.hawk.gwt.ppc.reflect;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.hawk.gwt.ppc.Configuration;
import org.hawk.gwt.ppc.Environment;
import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.reflect.lang.AnnotationClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.GWTClassType;
import org.hawk.gwt.ppc.reflect.lang.GWTGenericArrayType;
import org.hawk.gwt.ppc.reflect.lang.GWTParameterizedType;
import org.hawk.gwt.ppc.reflect.lang.GWTTypeVariable;
import org.hawk.gwt.ppc.reflect.lang.GWTWildCardType;
import org.hawk.gwt.ppc.scanner.SourcesLoader;
import org.hawk.gwt.ppc.scanner.utils.JavaArrayGenericTypeScope;
import org.hawk.gwt.ppc.scanner.utils.JavaClassDescriptor;
import org.hawk.gwt.ppc.scanner.utils.JavaConstructor;
import org.hawk.gwt.ppc.scanner.utils.JavaGenericTypeScope;
import org.hawk.gwt.ppc.scanner.utils.JavaMethod;
import org.hawk.gwt.ppc.scanner.utils.JavaModifier;
import org.hawk.gwt.ppc.scanner.utils.JavaParameterizedGenericTypeScope;
import org.hawk.gwt.ppc.scanner.utils.JavaTypeScope;
import org.hawk.gwt.ppc.scanner.utils.JavaTypeVariable;
import org.hawk.gwt.ppc.scanner.utils.JavaVariable;
import org.hawk.gwt.ppc.scanner.utils.JavaWildcardGenericTypeScope;
import org.hawk.gwt.ppc.utils.Name;

/**
 * Utility class for ClassDelegate subclasses generation.
 * Contains all the methods necessary for creating correct 
 * implementation of ClassDelegate to handle all the reflection
 * requests.
 * @author alex.bereznevatiy@gmail.com
 */
class ClassDelegateBuilder {
	private final static Map<String,String> WRAPPERS;
	static {
		Map<String,String> m = new HashMap<String,String>();
		m.put(Boolean.TYPE.getName(), Boolean.class.getName());
		m.put(Byte.TYPE.getName(), Byte.class.getName());
		m.put(Double.TYPE.getName(), Double.class.getName());
		m.put(Float.TYPE.getName(), Float.class.getName());
		m.put(Integer.TYPE.getName(), Integer.class.getName());
		m.put(Long.TYPE.getName(), Long.class.getName());
		m.put(Short.TYPE.getName(), Short.class.getName());
		m.put(Character.TYPE.getName(), Character.class.getName());
		WRAPPERS = Collections.unmodifiableMap(m);
	}

	private JavaTypeScope compilationUnit;
	private StringBuilder result;
	private List<String> thirdPartyClasses;
	private int[] indices;
	
	private ClassDelegateBuilder(JavaClassDescriptor classDescriptor){
		this.compilationUnit = classDescriptor.getCompilationUnit();
		this.thirdPartyClasses = new LinkedList<String>();
	}
	/**
	 * Creates definition of the ClassDelegateBuilder and initiates 
	 * the building of ClassDelegate subclass for the className specified.
	 * @param descriptor of the main class of the compilation unit
	 * @param loader sources loader to load the class
	 * @return ClassDelegateBuilder to perform any additional work
	 * @throws IOException in case of IO problems
	 * @throws ParseException in case of source parsing problems
	 */
	static ClassDelegateBuilder build(JavaClassDescriptor descriptor, SourcesLoader loader) throws ParseException, IOException {
		ClassDelegateBuilder result = new ClassDelegateBuilder(descriptor);
		if (descriptor.getCompilationUnit().isAnnotation()) {
			return result.new AnnotationDelegateBuilder(descriptor);
		}
		return result;
	}
	/**
	 * Writes the class metadata as {@link ClassDelegate} subclass to the {@link StringBuilder} provided.
	 * @param result - {@link StringBuilder} to write the data
	 */
	void write(StringBuilder result){
		result.append(this);
	}
	
	/**
	 * @return true if the class was built should be flushed into target class source
	 * 		and false to flush it to the entry point.
	 */
	boolean shouldBuildToClassSource() {
		return true;
	}

	public String toString() {
		buildClass();
		return result.toString();
	}
	
	/**
	 * Builds this class.
	 */
	void buildClass() {
		if (result != null) {
			return;
		}
		result = new StringBuilder();
		
		indent();
		header();
		method(JavaModifier.PROTECTED, "Class<?>[]", "getImplementedInterfaces", getImplementedInterfaces(), null);
		method(JavaModifier.PROTECTED, "String[]", "getConstructorSignatures", getConstructorSignatures(), null);
		method(JavaModifier.PROTECTED, "String[]", "getFieldsSignatures", getFieldsSignatures(), null);
		method(JavaModifier.PROTECTED, "String[]", "getMethodSignatures", getMethodSignatures(), null);
		List<String> varAgrMethods = isVarArg();
		if (!varAgrMethods.isEmpty()) {
			method(JavaModifier.PROTECTED, "boolean", "isVarArg", varAgrMethods, null, "Object accessor", "String signature");
		}
		method(JavaModifier.PROTECTED, "int[]", "getInterfacesIndices", getInterfacesIndices(), null, "Object accessor");
		method(JavaModifier.PUBLIC, "int", "getModifiers", getModifiers(), null, "Object accessor", "String signature");
		method(JavaModifier.PUBLIC, Name.getSimpleName(compilationUnit.getClassName()), "create", create(), "Throwable", "Object accessor", "String signature", "Object[] initargs");
		method(JavaModifier.PUBLIC, "Class<?>", "getFieldType", getFieldType(), null, "Object accessor", "String name");
		method(JavaModifier.PUBLIC, "Object", "get", get(), null, "Object accessor", "String name", "Object obj");
		method(JavaModifier.PUBLIC, "void", "set", set(), null, "Object accessor", "String name", "Object obj", "Object value");
		method(JavaModifier.PUBLIC, "Class<?>", "getReturnType", getReturnType(), null, "Object accessor", "String signature");
		method(JavaModifier.PUBLIC, "Object", "invoke", invoke(), "Throwable", "Object accessor", "String signature", "Object obj", "Object[] parameters");
		method(JavaModifier.PUBLIC, "Object", "createArray", createArray(compilationUnit.getClassName()), null, "Object accessor", "int[] dimensions");
		if (Environment.getInstance().getConfiguration().get(Configuration.GENERIC_SUPPORT_PARAMETER, Boolean.FALSE)) {
			buildGenericSupport();
		}
		result.append("\t};\n");
	}
	
	private List<String> getInterfacesIndices() {
		List<String> result = new LinkedList<String>();
		result.add("super.getInterfacesIndices(accessor);");
		StringBuilder line = new StringBuilder();
		line.append("return new int[]{");
		for (int i = 0; i < indices.length; i++) {
			if (i > 0) {
				line.append(',');
			}
			line.append(indices[i]);
		}
		line.append("};");
		result.add(line.toString());
		return result;
	}
	
	/**
	 * Sets interfaces indices to build against
	 * @param indices
	 */
	void setInterfaces(List<Integer> indices) {
		this.indices = new int[indices.size()];
		int i = 0;
		for (Integer val : indices) {
			this.indices[i++] = val;
		}
	}
	
	private List<String> isVarArg() {
		List<String> result = new LinkedList<String>();
		for (JavaMethod method : getAllMethods()) {
			if (!method.isVarArgs()) {
				continue;
			}
			StringBuilder line = new StringBuilder();
			if (result.isEmpty()) {
				line.append("return ");
			} else {
				line.append("\t|| ");
			}
			line.append('"');
			line.append(method.getSignature());
			line.append("\".equals(signature)");
			result.add(line.toString());
		}
		if (!result.isEmpty()) {
			int last = result.size() - 1;
			result.set(last, result.get(last) + ";");
			result.add(0, "super.isVarArg(accessor, signature);");
		}
		return result;
	}
	
	private List<JavaMethod> getAllMethods() {
		List<JavaMethod> methods = new LinkedList<JavaMethod>();
		methods.addAll(compilationUnit.getDeclaredConstructors());
		methods.addAll(compilationUnit.getDeclaredMethods());
		return methods;
	}
	
	private void buildGenericSupport() {
		method(JavaModifier.PUBLIC, "java.lang.reflect.TypeVariable<" + GWTClassType.class.getName() + "<" + compilationUnit.getResolvedClassName() + 
				">>[]", "getTypeParameters", getTypeParameters(), null);
		method(JavaModifier.PROTECTED, "Object[]", "getGenericInterfaces", getGenericInterfaces(), null, "Object accessor", "boolean allow");
		method(JavaModifier.PROTECTED, "Object", "getGenericSuperclass", getGenericSuperclass(), null, "Object accessor", "boolean allow");
		method(JavaModifier.PUBLIC, "<G extends java.lang.reflect.GenericDeclaration> " + GWTTypeVariable.class.getName() + 
				"<G>[]", "getMethodTypeParameters", getMethodTypeParameters(), null, "G m", "Object accessor", "String signature");
		
		method(JavaModifier.PUBLIC, "java.lang.reflect.Type[]", "getMethodGenericParameterTypes", getMethodGenericParameterTypes(), 
				null, "java.lang.reflect.GenericDeclaration declaration", "Object accessor", "String signature");
		
		method(JavaModifier.PUBLIC, "java.lang.reflect.Type", "getGenericReturnType", getGenericReturnType(), null, 
				"java.lang.reflect.Method declaration", "Object accessor", "String signature");
		
		method(JavaModifier.PUBLIC, "java.lang.reflect.Type", "getGenericFieldType", getGenericFieldType(), null, "Object accessor", "String signature");
	}
	
	private List<String> getGenericFieldType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getGenericFieldType(accessor, signature);");
		for (JavaVariable field : compilationUnit.getFields()) {
			JavaTypeScope type = field.getTypeDescriptor();
			if (type instanceof JavaGenericTypeScope) {
				StringBuilder line = new StringBuilder();
				line.append("if (signature.equals(\"");
				line.append(field.getName());
				line.append("\")) return ");
				createGenericTypeInitializer(line, (JavaGenericTypeScope)type);
				line.append(';');
				result.add(line.toString());
			}
			
		}
		result.add("return " + ClassDelegate.class.getName() + ".VOID;");
		return result;
	}
	private List<String> getGenericReturnType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getGenericReturnType(declaration, accessor, signature);");
		for (JavaMethod method : compilationUnit.getDeclaredMethods()) {
			JavaTypeScope type = method.getGenericReturnType();
			if (type instanceof JavaGenericTypeScope) {
				StringBuilder line = new StringBuilder();
				line.append("if (signature.equals(\"");
				line.append(method.getSignature());
				line.append("\")) return ");
				appendType(line, type);
				line.append(';');
				result.add(line.toString());
			}
		}
		result.add("return null;");
		return result;
	}
	private List<String> getMethodGenericParameterTypes() {
		List<String> result = new LinkedList<String>();
		result.add("super.getMethodGenericParameterTypes(declaration, accessor, signature);");
		for (JavaMethod method : getAllMethods()) {
			List<JavaTypeScope> params = method.getGenericParameterTypes();
			if (params == null) {
				continue;
			}
			StringBuilder line = new StringBuilder();
			line.append("if (signature.equals(\"");
			line.append(method.getSignature());
			line.append("\")) return new java.lang.reflect.Type[] { ");
			Iterator<JavaTypeScope> iterator = params.iterator();
					
			while (iterator.hasNext()) {
				appendType(line, iterator.next());
				if (iterator.hasNext()) {
					line.append(',');
				}
			}
			line.append(" };");
			result.add(line.toString());
		}
		result.add("return new java.lang.reflect.Type[0];");
		return result;
	}
	
	private List<String> getMethodTypeParameters() {
		List<String> result = new LinkedList<String>();
		result.add("super.getMethodTypeParameters(m, accessor, signature);");
		
		for (JavaMethod method : getAllMethods()) {
			Collection<JavaTypeVariable> vars =  method.getTypeParameters();
			if (vars.isEmpty()) {
				continue;
			}
			StringBuilder line = new StringBuilder();
			line.append("if (signature.equals(\"");
			line.append(method.getSignature());
			line.append("\")) return new ");
			line.append(GWTTypeVariable.class.getName());
			line.append("[]{ ");
			Iterator<JavaTypeVariable> iterator = vars.iterator();
			while (iterator.hasNext()) {
				JavaTypeVariable var = iterator.next();
				line.append("getTypeParameter(m, \"");
				line.append(var.getName());
				line.append('"');
				List<JavaTypeScope> bounds = var.getBounds();
				if (bounds.isEmpty()) {
					line.append(", Object.class");
				}
				for (JavaTypeScope boundType : bounds) {
					line.append(", ");
					appendType(line, boundType);
				}
				line.append(')');
				if (iterator.hasNext()) {
					line.append(", ");
				}
			}
			line.append(" };");
			result.add(line.toString());
		}
		
		result.add("return new " + GWTTypeVariable.class.getName() + "[0];");
		return result;
	}
	
	private static void appendType(StringBuilder result, JavaTypeScope type) {
		if (type instanceof JavaGenericTypeScope) {
			createGenericTypeInitializer(result, (JavaGenericTypeScope)type);
			return;
		}
		result.append(ClassDelegate.class.getName());
		result.append(".forClass(");
		result.append(type.getResolvedClassName());
		result.append(".class)");
	}
	
	private List<String> getImplementedInterfaces() {
		List<String> result = new LinkedList<String>();
		result.add("return new Class[]{");
		Iterator<JavaTypeScope>  interfaces = compilationUnit.getImplementedInterfaces().iterator();
		while(interfaces.hasNext()) {
			JavaTypeScope iFace = interfaces.next();
			thirdPartyClasses.add(iFace.getResolvedClassName());
			line(result, "\t", iFace.getResolvedClassName(), interfaces.hasNext() ? ".class," : ".class");
		}
		result.add("};");
		return result;
	}

	private List<String> getConstructorSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<JavaConstructor>  constructors = compilationUnit.getDeclaredConstructors().iterator();
		
		while(constructors.hasNext()) {
			JavaConstructor c = constructors.next();
			line(result, "\t\"", c.getSignature(), constructors.hasNext() ? "\"," : "\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getFieldsSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<JavaVariable>  fields = compilationUnit.getFields().iterator();
		while(fields.hasNext()) {
			JavaVariable fld = fields.next();
			line(result, "\t\"", fld.getName(), fields.hasNext() ? "\"," : "\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getMethodSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<JavaMethod>  methods = compilationUnit.getDeclaredMethods().iterator();
		while(methods.hasNext()) {
			line(result, "\t\"", methods.next().getSignature(), methods.hasNext() ? "\"," : "\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getModifiers() {
		List<String> result = new LinkedList<String>();
		result.add("int sign = 0;");
		Map<String,Integer>  signatures = getAllSignatures();
		for(String signature : signatures.keySet()) {
			if (result.size() == 1) {
				line(result,"if(signature.equals(\"",signature,
					"\"))sign|=",signatures.get(signature).toString(),";");
			} else {
				line(result,"else if(signature.equals(\"",signature,
					"\"))sign|=",signatures.get(signature).toString(),";");
			}
		}
		result.add("return super.getModifiers(accessor, signature)|sign;");
		return result;
	}
	
	private Map<String,Integer> getAllSignatures() {
		Map<String,Integer> result = new TreeMap<String, Integer>();
		
		for(JavaConstructor c : compilationUnit.getDeclaredConstructors()) {
			result.put(c.getSignature(), JavaModifier.getMask(c.getModifiers()));
		}
		
		for(JavaVariable fld : compilationUnit.getFields()) {
			result.put(fld.getName(), JavaModifier.getMask(fld.getModifiers()));
		}
		
		for(JavaMethod m : compilationUnit.getDeclaredMethods()) {
			result.put(m.getSignature(), JavaModifier.getMask(m.getModifiers()));
		}
		result.put("", JavaModifier.getMask(compilationUnit.getModifiers()));
		return result;
	}
	
	private List<String> create() {
		List<String> result = new LinkedList<String>();
		result.add("super.create(accessor, signature, initargs);");
		Iterator<JavaConstructor>  constructors = compilationUnit.getDeclaredConstructors().iterator();
		
		if (!compilationUnit.getModifiers().contains(JavaModifier.ABSTRACT) && !compilationUnit.isEnum()) {
			while(constructors.hasNext()) {
				JavaConstructor constructor = constructors.next();
				line(result,"if(signature.equals(\"",constructor.getSignature(),
					"\"))return new ",prepareMethodCall(constructor, "initargs"),";");
			}
		}
		result.add("return null;");
		return result;
	}
	
	private String prepareMethodCall(JavaMethod m, String argsParam) {
		StringBuilder result = new StringBuilder();

		result.append(m.getName());
		result.append('(');
		
		String[] values = m.getParametersTypes();
		for (int i = 0; i < values.length; i++) {
			if (i > 0) {
				result.append(',');
			}
			result.append('(');
			String type = Name.getRawName(values[i]);
			if (WRAPPERS.containsKey(type)) {
				type = WRAPPERS.get(type);
			}
			thirdPartyClasses.add(type);
			result.append(type);
			result.append(')');
			result.append(argsParam);
			result.append('[');
			result.append(i);
			result.append(']');
		}
		result.append(')');
		return result.toString();
	}
	
	private List<String> getFieldType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getFieldType(accessor, name);");
		Iterator<JavaVariable>  fields = compilationUnit.getFields().iterator();
		while(fields.hasNext()) {
			JavaVariable field = fields.next();
			line(result,"if(name.equals(\"",field.getName(),
					"\"))return ", Name.getRawName(field.getTypeName()),".class;");
		}
		result.add("return null;");
		return result;
	}
	
	private List<String> get() {
		List<String> result = new LinkedList<String>();
		result.add("super.get(accessor, name, obj);");
		Iterator<JavaVariable>  fields = compilationUnit.getFields().iterator();
		while(fields.hasNext()) {
			JavaVariable field = fields.next();
			int mod = JavaModifier.getMask(field.getModifiers());
			StringBuilder obj = new StringBuilder();
			if (!Modifier.isStatic(mod)) {
				obj.append("((");
				obj.append(compilationUnit.getResolvedClassName());
				obj.append(")obj).");
			} else {
				obj.append(compilationUnit.getResolvedClassName());
				obj.append(".");
			}
			line(result,"if(name.equals(\"", field.getName(),
					"\"))return ", obj.toString(), field.getName(), ";");
		}
		result.add("return null;");
		return result;
	}
	private List<String> set() {
		List<String> result = new LinkedList<String>();
		result.add("super.set(accessor, name, obj, value);");
		Iterator<JavaVariable>  fields = compilationUnit.getFields().iterator();
		while(fields.hasNext()) {
			JavaVariable field = fields.next();

			String typeName = Name.getRawName(field.getTypeName());
			if (WRAPPERS.containsKey(typeName)) {
				typeName = WRAPPERS.get(typeName);
			}
			int mod = JavaModifier.getMask(field.getModifiers());
			if(Modifier.isFinal(mod))continue;
			StringBuilder obj = new StringBuilder();
			if (!Modifier.isStatic(mod)) {
				obj.append("((");
				obj.append(compilationUnit.getResolvedClassName());
				obj.append(")obj).");
			} else {
				obj.append(compilationUnit.getResolvedClassName());
				obj.append(".");
			}
			if(result.size() == 1) {
				line(result,"if(name.equals(\"", field.getName(),
					"\"))", obj.toString(), field.getName(),"=(", typeName, ")value;");
			} else {
				line(result,"else if(name.equals(\"", field.getName(),
					"\"))", obj.toString(), field.getName(), "=(", typeName, ")value;");
			}
		}
		return result;
	}
	private List<String> getReturnType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getReturnType(accessor, signature);");
		Iterator<JavaMethod>  methods = compilationUnit.getDeclaredMethods().iterator();
		while(methods.hasNext()) {
			JavaMethod m = methods.next();
			line(result, "if(signature.equals(\"", m.getSignature(),
				"\"))return ", m.getReturnType(), ".class;");
		}
		result.add("return null;");
		return result;
	}
	private List<String> invoke() {
		List<String> result = new LinkedList<String>();
		result.add("super.invoke(accessor, signature, obj, parameters);");
		Iterator<JavaMethod>  methods = compilationUnit.getDeclaredMethods().iterator();
		while (methods.hasNext()) {
			JavaMethod m = methods.next();
			int mod = JavaModifier.getMask(m.getModifiers());
			StringBuilder obj = new StringBuilder();
			if (!Modifier.isStatic(mod)) {
				obj.append("((");
				obj.append(compilationUnit.getResolvedClassName());
				obj.append(")obj).");
			} else {
				obj.append(compilationUnit.getResolvedClassName());
				obj.append('.');
			}
			String retStatement = "return ";
			if (m.getReturnType().equals(Void.TYPE.getName())) {
				retStatement = "";
			}
			line(result, "if(signature.equals(\"", m.getSignature(),
					"\"))", retStatement, obj.toString(),
					prepareMethodCall(m, "parameters"), ";");
		}
		result.add("return null;");
		return result;
	}
	
	private List<String> getTypeParameters() {
		List<String> result = new LinkedList<String>();
		result.add("return new java.lang.reflect.TypeVariable[] {");
		if (compilationUnit.isClass() || compilationUnit.isInterface()) {
			Iterator<JavaTypeVariable> iterator = compilationUnit.getTypeVariables().iterator();
			while (iterator.hasNext()) {
				StringBuilder line = new StringBuilder();
				createGenericTypeVariableInitializer(line, iterator.next());
				if (iterator.hasNext()) {
					line.append(',');
				}
				result.add(line.toString());
				
			}
		}
		result.add("};");
		return result;
	}
	
	private List<String> getGenericSuperclass() {
		List<String> result = new LinkedList<String>();
		result.add("super.getGenericSuperclass(accessor, true);");
		if (compilationUnit.getGenericSuperclass() != null) {
			JavaGenericTypeScope type = compilationUnit.getGenericSuperclass();
			StringBuilder returnStatement = new StringBuilder();
			returnStatement.append("return ");
			createGenericTypeInitializer(returnStatement, type);
			returnStatement.append(';');
			result.add(returnStatement.toString());
		} else {
			result.add("return getSuperclass();");
		}
		return result;
	}
	
	private static void createGenericTypeInitializer(StringBuilder result, JavaGenericTypeScope type) {
		if (type instanceof JavaParameterizedGenericTypeScope) {
			createParameterizedGenericTypeInitializer(result, (JavaParameterizedGenericTypeScope)type);
		} else if (type instanceof JavaWildcardGenericTypeScope) {
			createWildcardGenericTypeInitializer(result, (JavaWildcardGenericTypeScope)type);
		} else if (type instanceof JavaArrayGenericTypeScope) {
			createArrayGenericTypeInitializer(result, (JavaArrayGenericTypeScope)type);
		} else if (type instanceof JavaTypeVariable) {
			createGenericTypeVariableInitializer(result, (JavaTypeVariable)type);
		} else {
			throw new InternalError("Unknown classification of generic type scope: " + type);
		}
	}
	
	private static void createGenericTypeVariableInitializer(StringBuilder result, JavaTypeVariable var) {
		result.append("getTypeParameter(");
		if (var.getOwner() instanceof JavaMethod) {
			result.append("declaration");
		} else {
			result.append("this");
		}
		result.append(", \"");
		result.append(var.getName());
		result.append('"');
		for (JavaTypeScope boundType : var.getBounds()) {
			result.append(", ");
			appendType(result, boundType);
		}
		result.append(')');
	}
	
	private static void createArrayGenericTypeInitializer(StringBuilder result, JavaArrayGenericTypeScope type) {
		result.append("new ");
		result.append(GWTGenericArrayType.class.getName());
		result.append("(accessor, ");
		JavaTypeScope component = type.getComponentType();
		if (component instanceof JavaGenericTypeScope) {
			createGenericTypeInitializer(result, (JavaGenericTypeScope)component);
		} else {
			result.append(component.getResolvedClassName());
			result.append(".class");
		}
		result.append(')');
	}
	
	private static void createWildcardGenericTypeInitializer(StringBuilder result, JavaWildcardGenericTypeScope type) {
		result.append("new ");
		result.append(GWTWildCardType.class.getName());
		result.append("(accessor, ");
		JavaTypeScope lower = type.getLowerBound();
		if (lower == null) {
			result.append("null");
		} else if (lower instanceof JavaGenericTypeScope) {
			createGenericTypeInitializer(result, (JavaGenericTypeScope)lower);
		} else {
			result.append(lower.getResolvedClassName());
			result.append(".class");
		}
		for (JavaTypeScope param : type.getUpperBounds()) {
			result.append(',');
			if (param == null) {
				result.append("null");
			} else if (param instanceof JavaGenericTypeScope) {
				createGenericTypeInitializer(result, (JavaGenericTypeScope)param);
			} else {
				result.append(param.getResolvedClassName());
				result.append(".class");
			}
		}
		result.append(')');
	}
	
	private static void createParameterizedGenericTypeInitializer(StringBuilder result, JavaParameterizedGenericTypeScope type) {
		result.append("new ");
		result.append(GWTParameterizedType.class.getName());
		result.append("(accessor, ");
		result.append(type.getResolvedClassName());
		result.append(".class");
		for (JavaTypeScope param : type.getTypeParameters()) {
			result.append(',');
			if (param == null) {
				result.append("null");
			} else if (param instanceof JavaGenericTypeScope) {
				createGenericTypeInitializer(result, (JavaGenericTypeScope)param);
			} else {
				result.append(param.getResolvedClassName());
				result.append(".class");
			}
		}
		result.append(')');
	}
	
	private List<String> getGenericInterfaces() {
		List<String> result = new LinkedList<String>();
		result.add("super.getGenericInterfaces(accessor, true);");
		List<JavaTypeScope> ifaces = compilationUnit.getGenericInterfaces();
		if (ifaces != null) {
			result.add("return new Object[]{");
			Iterator<JavaTypeScope> iterator = ifaces.iterator();
			while (iterator.hasNext()) {
				JavaTypeScope type = iterator.next();
				StringBuilder returnStatement = new StringBuilder();
				if (type instanceof JavaGenericTypeScope) {
					createGenericTypeInitializer(returnStatement, (JavaGenericTypeScope)type);
				} else {
					returnStatement.append(type.getResolvedClassName());
					returnStatement.append(".class");
				}
				
				if (iterator.hasNext()) {
					returnStatement.append(',');
				}
				result.add(returnStatement.toString());
			}
			result.add("};");
		} else {
			result.add("return getInterfaces();");
		}
		return result;
	}

	private List<String> createArray(String className) {
		List<String> result = new LinkedList<String>();
		result.add("super.createArray(accessor, dimensions);");
		result.add("switch (dimensions.length) {");
		result.add("case 1: return new " + className + "[dimensions[0]];");
		result.add("case 2: return new " + className + "[dimensions[0]][dimensions[1]];");
		result.add("case 3: return new " + className + "[dimensions[0]][dimensions[1]][dimensions[2]];");
		result.add("case 4: return new " + className + "[dimensions[0]][dimensions[1]][dimensions[2]][dimensions[3]];");
		result.add("case 5: return new " + className + "[dimensions[0]][dimensions[1]][dimensions[2]][dimensions[3]][dimensions[4]];");
		result.add("}");
		result.add("return null;");
		return result;
	}
	
	private void line(List<String> result,String...parts){
		if(parts.length==0){
			return;
		}
		if(parts.length==1){
			result.add(parts[0]);
			return;
		}
		StringBuilder builder = new StringBuilder();
		for(String part : parts){
			builder.append(part);
		}
		result.add(builder.toString());
	}
	
	private void method(JavaModifier mod, String returnType, String name,
			List<String> body, String exceptions, String...params) {
		result.append('\n');
		indent().indent();		
		modifiers(mod);
		result.append(' ');
		result.append(returnType);
		result.append(' ');
		result.append(name);
		result.append('(');
		for(int i=0;i<params.length;i++){
			if(i>0)result.append(',');
			result.append(params[i]);
		}
		result.append(')');
		if (exceptions != null) {
			result.append(" throws ");
			result.append(exceptions);
		}
		result.append('{');
		result.append('\n');
		for(String line : body){
			indent().indent().indent();
			result.append(line);
			result.append('\n');
		}
		indent().indent();
		result.append('}');
		result.append('\n');
	}
	
	private void header() {
		String classSimpleName = Name.getSimpleName(compilationUnit.getClassName());
		
		if (compilationUnit.isEnum()) {
			result.append(';');
		}
		
		modifiers(JavaModifier.PUBLIC,JavaModifier.STATIC,JavaModifier.FINAL);
		result.append(' ');
		result.append(ClassDelegate.class.getName());
		result.append('<');
		result.append(classSimpleName);
		result.append("> $class = \n");
		indent();
		result.append("new ");
		result.append(ClassDelegate.class.getName());
		result.append('<');
		result.append(classSimpleName);
		result.append(">(");
		result.append(classSimpleName);
		result.append(".class) {");
	}
	
	private void modifiers(JavaModifier...modifiers) {
		boolean space = false;
		for(JavaModifier mod : modifiers){
			if(mod.equals(JavaModifier.DEFAULT))continue;
			if(space)result.append(' ');
			else space = true;
			result.append(mod.name().toLowerCase());
		}
	}
	
	private ClassDelegateBuilder indent() {
		result.append('\t');
		return this;
	}
	
	/**
	 * Builds {@link ClassDelegate} subclasses for annotations.
	 * This is necessary since GWT processes annotations in different way than usual classes.
	 * 
	 * @author alex.bereznevatiy@gmail.com
	 */
	private class AnnotationDelegateBuilder extends ClassDelegateBuilder {

		private AnnotationDelegateBuilder(JavaClassDescriptor classDescriptor) {
			super(classDescriptor);
		}

		void buildClass() {
			if (result != null) {
				return;
			}
			ClassDelegateBuilder.this.result = new StringBuilder();
			
			indent();
			header();
			List<String> varAgrMethods = isVarArg();
			if (!varAgrMethods.isEmpty()) {
				method(JavaModifier.PROTECTED, "boolean", "isVarArg", varAgrMethods, null, "Object accessor", "String signature");
			}
			method(JavaModifier.PROTECTED, "String[]", "getMethodSignatures", getMethodSignatures(), null);
			method(JavaModifier.PUBLIC, "int", "getModifiers", getModifiers(), null, "Object accessor", "String signature");
			method(JavaModifier.PUBLIC, "Class<?>", "getReturnType", getReturnType(), null, "Object accessor", "String signature");
			method(JavaModifier.PUBLIC, "Object", "invoke", invoke(), "Throwable", "Object accessor", "String signature", "Object obj", "Object[] parameters");
			method(JavaModifier.PUBLIC, "Object", "createArray", createArray(compilationUnit.getResolvedClassName()), null, "Object accessor", "int[] dimensions");
			if (Environment.getInstance().getConfiguration().get(Configuration.GENERIC_SUPPORT_PARAMETER, Boolean.FALSE)) {
				buildGenericSupport();
			}
			result.append("}");
		}

		@Override
		boolean shouldBuildToClassSource() {
			return false;
		}
		
		private void header() {
			result.append("new ");
			result.append(AnnotationClassDelegate.class.getName());
			result.append('<');
			result.append(compilationUnit.getResolvedClassName());
			result.append(">(");
			result.append(compilationUnit.getResolvedClassName());
			result.append(".class) {");
			indent();
		}
		
		public String toString() {
			buildClass();
			return result.toString();
		}
	}

	public List<String> getThirdPartyClasses() {
		return thirdPartyClasses;
	}
}
